/*
 * Copyright 2001-2014 Viewstreet Technologies www.viewstreet.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.viewstreet.beanspice.processor;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * The Class SourceParser. provides the parsing capability for Java Source files
 */
public class SourceParser {

	/** The classpath. */
	private List<String> classpath;
	
	/** The parser. */
	private ASTParser parser ;
	
	/** The Constant AST_LEVEL. */
	protected static final int AST_LEVEL = AST.JLS4;
	
	/** The Constant JDK_LEVEL. */
	protected static final long JDK_LEVEL = ClassFileConstants.JDK1_5;
	
	/** The logger. */
	private static Logger logger = LoggerFactory.getLogger(SourceParser.class);
	
	/**
	 * The Constructor.
	 *
	 * @param classpath the classpath
	 */
	public SourceParser(List<String> classpath){
		if(classpath == null)	{
			this.classpath = new ArrayList<String> ();
		}	else {
			this.classpath = classpath;
		}
		parser = ASTParser.newParser(AST_LEVEL);
	}
	
	/**
	 * Gets the contents of a file
	 *
	 * @param sourceFile the source file
	 * @return the contents
	 * @throws FileNotFoundException the file not found exception
	 */
	public static  String getContents(File sourceFile) throws FileNotFoundException	{
		Scanner srcScanner = new Scanner(sourceFile);
		String content = null;
		try {
			content = srcScanner.useDelimiter("\\Z").next();
		} finally {
			srcScanner.close();
		}
		return content;
	}
	

	/**
	 * Creates a temporary type from the provided string content. The contents can
	 * include methods and properties, need not have any class declaration
	 *
	 * @param content the content
	 * @return the type declaration
	 */
	public TypeDeclaration createType(String content)	{
		parser.setKind(ASTParser.K_CLASS_BODY_DECLARATIONS);
		parser.setSource(content.toCharArray());
		return (TypeDeclaration) parser.createAST(null);
	}
	
	
	/**
	 * Run conversion. Creates a compilation unit of the given source file. 
	 * This method will not throw an exception for compilation errors as an initial
	 * parse of source files is all that may be required. in order to test the successful
	 * compilation see checkCompilation
	 *
	 * @param sourceFile the source file
	 * @param srcFilePaths the src file paths
	 * @return the compilation unit
	 * @throws Exception the exception
	 */
	public CompilationUnit runConversion(
			File sourceFile,
			File []srcFilePaths) throws Exception {

		String content = getContents(sourceFile);
		
		return runConversion(content.toCharArray(), sourceFile.getName(), srcFilePaths);
	}

	/**
	 * Run conversion. Creates a compilation unit of the given source file. 
	 * This method will not throw an exception for compilation errors as an initial
	 * parse of source files is all that may be required. in order to test the successful
	 * compilation see checkCompilation
	 *
	 * @param source the source
	 * @param unitName the unit name
	 * @param srcFilePaths the src file paths
	 * @return the compilation unit
	 * @throws Exception the exception
	 */
	public CompilationUnit runConversion(
			char[] source,
			String unitName,
			File [] srcFilePaths) throws Exception {

		String [] srcPaths = new String[srcFilePaths.length];
		int pos =0;
		for (File file : srcFilePaths) {
			srcPaths[pos++] = file.getAbsolutePath();
		}

		String[] classpathArray = new String[classpath.size()];
		classpath.toArray(classpathArray);
		ASTParser parser = ASTParser.newParser(AST_LEVEL);
		parser.setSource(source);
		parser.setEnvironment(classpathArray, srcPaths, null, true);
		parser.setResolveBindings(true);
		parser.setStatementsRecovery(true);
		parser.setBindingsRecovery(true);
		parser.setCompilerOptions(getCompilerOptions());
		parser.setUnitName(unitName);
		CompilationUnit cu = (CompilationUnit)parser.createAST(null);
		
		return cu;
	}
	
	
	/**
	 * Check compilation. Will log any errors and warnings and additionally 
	 * throw an exception upon a compilation error
	 *
	 * @param cu the cu
	 * @param unitName the unit name
	 * @throws Exception the exception
	 */
	public void checkCompilation(CompilationUnit cu,String unitName) throws Exception	{
		boolean containsErrors = false;
		for (IProblem problem : cu.getProblems()) {
			if(problem.isError()){
				logger.error("Error compiling "+unitName +" "+problem.getMessage());
				containsErrors = true;
			} else if(problem.isWarning()){
				logger.warn("Warning compiling "+unitName +" "+problem.getMessage());
			}	else {
				logger.info("Message during compiling "+unitName +" "+problem.getMessage());
			}
		}
		
		if(containsErrors)	{
			throw new Exception("Stoping due to prior errors with "+unitName);
		}
	}
	
	/**
	 * Gets the compiler options.
	 *
	 * @return the compiler options
	 */
	private Map<String, String> getCompilerOptions() {
		Map<String, String> defaultOptions = getCompilerComplianceOptions(JDK_LEVEL);
		defaultOptions.put(CompilerOptions.OPTION_LocalVariableAttribute, CompilerOptions.GENERATE);
		defaultOptions.put(CompilerOptions.OPTION_ReportUnusedPrivateMember, CompilerOptions.WARNING);
		defaultOptions.put(CompilerOptions.OPTION_ReportUnusedImport, CompilerOptions.WARNING);
		defaultOptions.put(CompilerOptions.OPTION_ReportLocalVariableHiding, CompilerOptions.WARNING);
		defaultOptions.put(CompilerOptions.OPTION_ReportFieldHiding, CompilerOptions.WARNING);
		defaultOptions.put(CompilerOptions.OPTION_ReportPossibleAccidentalBooleanAssignment, CompilerOptions.WARNING);
		defaultOptions.put(CompilerOptions.OPTION_ReportSyntheticAccessEmulation, CompilerOptions.WARNING);
		defaultOptions.put(CompilerOptions.OPTION_PreserveUnusedLocal, CompilerOptions.PRESERVE);
		defaultOptions.put(CompilerOptions.OPTION_ReportUnnecessaryElse, CompilerOptions.WARNING );
		defaultOptions.put(CompilerOptions.OPTION_ReportDeadCode, CompilerOptions.WARNING);
		return defaultOptions;
	}
	
	
	/**
	 * Gets the compiler compliance options.
	 *
	 * @param complianceLevel the compliance level
	 * @return the compiler compliance options
	 */
	private Map<String, String> getCompilerComplianceOptions(long complianceLevel) {
		@SuppressWarnings("unchecked")
		Map<String, String> options = new CompilerOptions().getMap();
		options.put(CompilerOptions.OPTION_ReportUnusedLocal, CompilerOptions.IGNORE);
		if (complianceLevel == ClassFileConstants.JDK1_3) {
			options.put(CompilerOptions.OPTION_Compliance, CompilerOptions.VERSION_1_3);
			options.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_1_3);
			options.put(CompilerOptions.OPTION_TargetPlatform, CompilerOptions.VERSION_1_1);
		} else if (complianceLevel == ClassFileConstants.JDK1_4) {
			options.put(CompilerOptions.OPTION_Compliance, CompilerOptions.VERSION_1_4);
			options.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_1_4);
			options.put(CompilerOptions.OPTION_TargetPlatform, CompilerOptions.VERSION_1_4);
		} else if (complianceLevel == ClassFileConstants.JDK1_5) {
			options.put(CompilerOptions.OPTION_Compliance, CompilerOptions.VERSION_1_5);
			options.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_1_5);
			options.put(CompilerOptions.OPTION_TargetPlatform, CompilerOptions.VERSION_1_5);
		} else if (complianceLevel == ClassFileConstants.JDK1_6) {
			options.put(CompilerOptions.OPTION_Compliance, CompilerOptions.VERSION_1_6);
			options.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_1_6);
			options.put(CompilerOptions.OPTION_TargetPlatform, CompilerOptions.VERSION_1_6);
		} else if (complianceLevel == ClassFileConstants.JDK1_7) {
			options.put(CompilerOptions.OPTION_Compliance, CompilerOptions.VERSION_1_7);
			options.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_1_7);
			options.put(CompilerOptions.OPTION_TargetPlatform, CompilerOptions.VERSION_1_7);
//		} else if (complianceLevel == ClassFileConstants.JDK1_8) {
//			options.put(CompilerOptions.OPTION_Compliance, CompilerOptions.VERSION_1_8);
//			options.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_1_8);
//			options.put(CompilerOptions.OPTION_TargetPlatform, CompilerOptions.VERSION_1_8);
		}
		return options;
	}

	
	
}
